<template>
  <div>
    <div class="swap">
      <div class="g-form form">
        <div class="head">
          <div class="title">
            <h2>Swap</h2>
            <span>Trade Token is an instant</span>
          </div>
          <div class="setting">
            <div class="g-clickable" @click="quote">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 19 19"
                id="arrow_loading"
                class="arrow-loading"
              >
                <path
                  stroke="none"
                  fill="var(--color-light)"
                  d="M17.61907,6.64429064 L13.8617197,6.64429064 C13.3840439,6.64429064 12.9914138,6.25155686 12.9914138,5.77398471 C12.9914138,5.2963089 13.3840439,4.90357512 13.8617197,4.90357512 L15.3689095,4.90357512 C14.0103035,2.98248087 11.7707673,1.73006538 9.2340637,1.73006538 C5.0840575,1.73006538 1.7194412,5.09468172 1.7194412,9.24468791 C1.7194412,13.3946681 5.0840575,16.7592845 9.2340637,16.7592845 C13.3840439,16.7592845 16.74866,13.3946681 16.74866,9.24468791 C16.74866,8.76701211 17.141394,8.37438191 17.61907,8.37438191 C18.096616,8.37438191 18.48935,8.76701211 18.48935,9.24468791 C18.48935,14.3498902 14.3498902,18.4893498 9.2446879,18.4893498 C4.139356,18.4893498 0,14.3498902 0,9.24468791 C0,4.13935601 4.139356,0 9.2446879,0 C12.3438798,0 15.0823402,1.52833455 16.759284,3.86343695 L16.759284,2.31382802 C16.759284,1.83615222 17.152018,1.44352209 17.62959,1.44352209 C18.10724,1.44352209 18.5,1.83615222 18.5,2.31382802 L18.5,5.78450529 C18.478726,6.26218109 18.096616,6.64429064 17.61907,6.64429064 Z"
                ></path>
                <defs>
                  <path
                    id="arrow"
                    stroke="none"
                    fill="none"
                    d="M17.61907,6.64429064 L13.8617197,6.64429064 C13.3840439,6.64429064 12.9914138,6.25155686 12.9914138,5.77398471 C12.9914138,5.2963089 13.3840439,4.90357512 13.8617197,4.90357512 L15.3689095,4.90357512 C14.0103035,2.98248087 11.7707673,1.73006538 9.2340637,1.73006538 C5.0840575,1.73006538 1.7194412,5.09468172 1.7194412,9.24468791 C1.7194412,13.3946681 5.0840575,16.7592845 9.2340637,16.7592845 C13.3840439,16.7592845 16.74866,13.3946681 16.74866,9.24468791 C16.74866,8.76701211 17.141394,8.37438191 17.61907,8.37438191 C18.096616,8.37438191 18.48935,8.76701211 18.48935,9.24468791 C18.48935,14.3498902 14.3498902,18.4893498 9.2446879,18.4893498 C4.139356,18.4893498 0,14.3498902 0,9.24468791 C0,4.13935601 4.139356,0 9.2446879,0 C12.3438798,0 15.0823402,1.52833455 16.759284,3.86343695 L16.759284,2.31382802 C16.759284,1.83615222 17.152018,1.44352209 17.62959,1.44352209 C18.10724,1.44352209 18.5,1.83615222 18.5,2.31382802 L18.5,5.78450529 C18.478726,6.26218109 18.096616,6.64429064 17.61907,6.64429064 Z"
                  ></path>
                  <clipPath id="arrow-clip">
                    <use xlink:href="#arrow"></use>
                  </clipPath>
                </defs>
                <g clip-path="url(#arrow-clip)">
                  <circle
                    cx="7.02"
                    cy="7.02"
                    r="4.69"
                    transform="rotate(365,12,12)"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="16"
                    stroke-dasharray="30"
                    stroke-dashoffset="0"
                  >
                    <animate
                      attributeName="stroke-dashoffset"
                      values="0;-30"
                      begin="arrow_loading.click; 0.7s"
                      repeatCount="indefinite"
                      dur="9.3s"
                    ></animate>
                  </circle>
                </g>
                <use xlink:href="#arrow"></use>
                <animateTransform
                  id="transform_0"
                  attributeName="transform"
                  attributeType="XML"
                  type="rotate"
                  from="0 0 0"
                  to="-10 0 0"
                  dur="0.07s"
                  begin="arrow_loading.click;"
                  repeatCount="1"
                ></animateTransform>
                <animateTransform
                  id="transform_1"
                  attributeName="transform"
                  attributeType="XML"
                  type="rotate"
                  from="-45 0 0"
                  to="390 0 0"
                  dur="0.6s"
                  begin="transform_0.end"
                  repeatCount="1"
                ></animateTransform>
                <animateTransform
                  id="transform_2"
                  attributeName="transform"
                  attributeType="XML"
                  type="rotate"
                  from="390 0 0"
                  to="360 0 0"
                  dur="0.15s"
                  begin="transform_1.end"
                  repeatCount="1"
                ></animateTransform>
              </svg>
            </div>
            <div class="g-clickable" @click="isShowTransactions = true">
              <i class="iconfont icon-history"></i>
            </div>
            <div class="g-clickable" @click="isShowSetting = true">
              <i class="iconfont icon-setting"></i>
            </div>
          </div>
        </div>
        <SwapField
          v-model="inAmount"
          type="from"
          :token="from"
          @select="showSelectToken"
        />
        <div class="switch">
          <div class="g-clickable icon" @click="change">
            <i class="iconfont icon-switch"></i>
          </div>
        </div>
        <SwapField
          v-model="outAmount"
          type="to"
          :token="to"
          @select="showSelectToken"
        />
        <SwapLabel name="Price" class="mt20" :value="price" />
        <SwapLabel name="Slippage tolerance" :value="`${tolerance}%`" />
        <SwapButton
          :amount="inAmount"
          :isSwap="true"
          :aggCode="aggCode"
          @confirm="confirm"
        />
        <div v-if="showMore">
          <SwapLabel
            name="Maximum Sold"
            class="mt20"
            help="1"
            value="10 USDT"
          />
          <SwapLabel
            name="Price Impact"
            help="1"
            value="< 0.01%"
            :blue="true"
          />
          <SwapLabel name="Liquidity Provider Fee" help="1" value="0.03 USDT" />
          <SwapLabel name="Route" help="1">
            <div class="route">
              <img src="@/assets/token/icon-usdt.svg" />
              <span>USDT</span>
              <i class="iconfont icon-arrow-down2"></i>
              <img src="@/assets/token/icon-usdc.svg" />
              <span>USDC</span>
              <i class="iconfont icon-arrow-down2"></i>
              <img src="@/assets/token/icon-dai.svg" />
              <span>DAI</span>
            </div>
          </SwapLabel>
        </div>
      </div>
      <div class="g-form form">
        <div class="head">
          <div class="title">
            <h2>Select a route to perform a swap</h2>
            <span>Best route is selected based on net output after gas.</span>
          </div>
        </div>
        <div class="aggregator" v-if="showList.length > 0">
          <div class="aggregator__list">
            <div
              class="aggregator__item"
              v-for="(item, i) in showList"
              :key="i"
            >
              <div
                class="g-clickable-small"
                :class="{ on: item.code === aggCode }"
                @click="changeAggCode(item)"
              >
                <div class="amount">
                  <span>{{ item.outAmount }} {{ item.symbol }}</span>
                  <span :class="{ best: item.change === 'BEST' }">{{
                    item.change
                  }}</span>
                </div>
                <div class="fee">
                  <span>{{ item.afterFee }}</span>
                  <span><i class="iconfont icon-gas"></i>{{ item.fee }}</span>
                  <span>{{ item.name }}</span>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="waiting" v-else>
          <div class="waiting__head">
            <div class="logo">
              <svg viewBox="0 0 108 108">
                <g id="logo" fill-rule="nonzero">
                  <path
                    d="M37.9818137,38.6102203 L19.4058137,57.6182203 C15.3018137,61.7222203 15.3018137,68.6342203 19.4058137,72.9542203 C23.5098137,77.0582203 30.2058137,77.0582203 34.3098137,72.9542203 L67.5738137,38.6102203 C59.5818137,30.1862203 46.1898137,30.1862203 37.9818137,38.6102203 Z M88.3098137,37.0982203 C84.2058137,32.9942203 77.5098137,32.9942203 73.4058137,37.0982203 L40.3578137,71.4422203 C48.5658137,79.8662203 61.7418137,79.8662203 69.9498137,71.4422203 L88.5258137,52.4342203 C92.4678137,48.0602203 92.3598137,41.3642203 88.3098137,37.0982203 Z"
                    id="Shape"
                    fill="#FFFFFF"
                  ></path>
                </g>
              </svg>
            </div>
            <div class="logos">
              <div class="scroll">
                <div v-for="(item, i) in aggregators" :key="i">
                  <img :src="item.icon" />
                </div>
                <div v-for="(item, i) in aggregators" :key="100 + i">
                  <img :src="item.icon" />
                </div>
              </div>
            </div>
          </div>
          <div class="waiting__title">
            <span>The Aggregator of Aggregators</span>
          </div>
          <div class="waiting__feature">
            <div>
              <i class="iconfont icon-coffee"></i>
              <span>Totally Free</span>
            </div>
            <div>
              <i class="iconfont icon-gasfee"></i>
              <span>Gas Estimation</span>
            </div>
            <div>
              <i class="iconfont icon-privacy"></i>
              <span>Preserves Privacy</span>
            </div>
          </div>
          <div class="waiting__content">
            <span>
              TokenSwap looks for the best route for your trade among a variety
              of Dex Aggregators, guaranteeing you the best execution prices in
              DeFi.
            </span>
          </div>
        </div>
        <div class="arrow-container">
          <div class="arrow"></div>
          <div class="arrow arrow-bottom"></div>
        </div>
      </div>
    </div>
    <NouDialog close-on-click-modal :visible.sync="isShowConfirm">
      <SwapConfirm
        :in-amount="inAmount"
        :out-amount="outAmount"
        :min-out-amount="minOutAmount"
        @close="isShowConfirm = false"
        @confirm="confirmSwap"
      />
    </NouDialog>
    <NouDialog close-on-click-modal :visible.sync="isShowSetting">
      <SwapSetting @close="isShowSetting = false" />
    </NouDialog>
    <NouDialog close-on-click-modal :visible.sync="isShowToken">
      <SwapToken @close="isShowToken = false" @select="hideSelectToken" />
    </NouDialog>
    <BlurMask close-on-click-modal :visible.sync="isShowTransactions">
      <Transactions @close="isShowTransactions = false" />
    </BlurMask>
  </div>
</template>

<script>
import SwapField from "@/components/SwapField";
import SwapLabel from "@/components/SwapLabel";
import SwapButton from "@/components/SwapButton";
import SwapConfirm from "@/components/SwapConfirm";
import SwapSetting from "@/components/SwapSetting";
import SwapToken from "@/components/SwapToken";
import NouDialog from "@/components/common/NouDialog";
import Transactions from "@/components/Transactions";
import BlurMask from "@/components/common/BlurMask";

import store from "@/store";
import { mapState } from "vuex";
import { getBalanceOf } from "@/init";
import { Aggregators, getProxyContract } from "@/config/config";
import { swap, getAdapterRoutes } from "@/api/swap";
import { adapters } from "@/api/aggregator/index";
import { decimals2Amount, amount2Decimals } from "@/utils/number";
import BigNumber from "bignumber.js";
import { toFixed } from "@/utils/format";
import { submitTransactions } from "@/api/core";

export default {
  components: {
    SwapField,
    SwapLabel,
    SwapButton,
    SwapConfirm,
    SwapSetting,
    SwapToken,
    NouDialog,
    BlurMask,
    Transactions,
  },
  computed: {
    ...mapState({
      from: (state) => state.from,
      to: (state) => state.to,
      account: (state) => state.account,
      tolerance: (state) => state.tolerance,
      showMore: (state) => state.showMore,
      chainCode: (state) => state.chainCode,
      nativeCurrency: (state) => state.nativeCurrency,
      signer: (state) => state.signer,
    }),
    price() {
      if (this.outAmount && this.inAmount) {
        const amount = this.outAmount / this.inAmount;
        return `${amount} ${this.to.symbol} Per ${this.from.symbol}`;
      }
      return "--";
    },
    adapters() {
      return adapters.filter((item) => {
        return !!item.chainToId[this.chainCode];
      });
    },
    showList() {
      const list = this.quoteList.sort((a, b) => {
        return a.outAmount > b.outAmount ? -1 : 1;
      });
      if (!list || !list.length) return [];

      const { outAmount } = list[0];
      return list.map((item, i) => {
        return {
          ...item,
          change:
            i === 0
              ? "BEST"
              : Math.floor(((item.outAmount - outAmount) / outAmount) * 10000) /
                  100 +
                "%",
        };
      });
    },
    bestAdapter() {
      if (!this.showList || !this.showList.length) return null;
      if (!this.aggCode) return this.showList[0];
      return this.showList.find((item) => {
        return item.code === this.aggCode;
      });
    },
  },
  data() {
    return {
      isSelectType: "from",
      isShowConfirm: false,
      isShowSetting: false,
      isShowToken: false,
      isShowTransactions: false,
      inAmount: "0",
      outAmount: "0",
      minOutAmount: "0",
      timeout: null,
      aggregators: Aggregators,
      aggCode: "",
      quoteList: [],
      quoteData: null,
    };
  },
  watch: {
    "from.address"() {
      this.inAmount = 1;
      this.aggCode = "";
      this.quote();
      getBalanceOf("from");
    },
    "to.address"() {
      this.aggCode = "";
      this.quote();
      getBalanceOf("to");
    },
    account() {
      this.aggCode = "";
      this.getBalance();
      this.quote();
    },
    inAmount() {
      this.quote();
    },
  },
  mounted() {
    this.inAmount = "1";
    this.getBalance();
  },
  methods: {
    async getBalance() {
      await getBalanceOf("from");
      await getBalanceOf("to");
    },
    change() {
      store.commit("SET_SWITCH");
    },
    quote() {
      clearTimeout(this.timeout);
      this.timeout = setTimeout(() => {
        this.doQuote();
      }, this.timeout);
    },
    async doQuote() {
      if (!this.account) return;

      this.quoteList = [];
      const userAddress = getProxyContract(this.chainCode);
      adapters.map((adapter) =>
        getAdapterRoutes({
          adapter,
          chain: this.chainCode,
          from: this.from.address,
          to: this.to.address,
          amount: amount2Decimals(this.inAmount, this.from.decimals),
          extra: {
            fromToken: this.from,
            toToken: this.to,
            userAddress,
            slippage: this.tolerance || 1,
          },
        }).then((res) => {
          this.setAdapter(res);
        })
      );
    },
    hideSelectToken(token) {
      if (this.isSelectType === "from") {
        store.commit("SET_FROM", token);
      } else {
        store.commit("SET_TO", token);
      }
      this.isShowToken = false;
    },
    showSelectToken(type) {
      this.isSelectType = type;
      this.isShowToken = true;
    },
    changeAggCode(val) {
      const { code, outAmount } = val;
      this.aggCode = code;
      this.outAmount = outAmount;
      this.from.allowance = 0;
      store.commit("SET_FROM", this.from);
      this.getBalance();
    },
    setAdapter(res, update) {
      const { name, price, gasPrice } = res;
      if (!price) return;

      const { amountReturned, estimatedGas, rawQuote, tokenApprovalAddress } =
        price || {};
      if (!amountReturned || !estimatedGas) return;
      // 因为gasprice单位是gwei，需要再转换一下精度
      const fee =
        decimals2Amount(
          new BigNumber(estimatedGas)
            .multipliedBy(new BigNumber(gasPrice))
            .toFixed(0),
          9
        ) * this.nativeCurrency.usd;
      const amount = decimals2Amount(amountReturned, this.to.decimals);
      console.log("decimals2Amount", amount);
      const item = {
        outAmount: toFixed(
          decimals2Amount(amountReturned, this.to.decimals),
          4
        ),
        symbol: this.to.symbol,
        change: "BEST",
        afterFee: `≈ $${toFixed(amount * this.to.usd - fee, 4)} after fees`,
        fee: `≈ $${toFixed(fee, 4)}`,
        code: name,
        name: name,
        rawQuote,
        tokenApprovalAddress,
      };
      if (update) {
        for (var i = 0; i < this.quoteList.length; i++) {
          if (this.quoteList[i].code === name) {
            this.$set(this.quoteList, i, item);
          }
        }
      } else {
        this.quoteList.push(item);
      }
      this.outAmount = this.bestAdapter && this.bestAdapter.outAmount || 0;
    },
    async confirm() {
      const adapter = adapters.find((item) => {
        return item.name === this.bestAdapter.code;
      });
      const userAddress = getProxyContract(this.chainCode);
      this.quoteData = {
        adapter,
        chain: this.chainCode,
        from: this.from.address,
        to: this.to.address,
        amount: amount2Decimals(this.inAmount, this.from.decimals),
        extra: {
          fromToken: this.from,
          toToken: this.to,
          userAddress,
          slippage: this.tolerance || 1,
        },
      };
      const res = await getAdapterRoutes(this.quoteData);
      this.setAdapter(res, true);
      this.isShowConfirm = true;
    },
    async confirmSwap() {
      this.isShowConfirm = false;
      const { chain, from, to, amount, extra } = this.quoteData;
      const { slippage } = extra || {};
      const { code, rawQuote } = this.bestAdapter;
      const { hash } = await swap({
        chain,
        from,
        to,
        amount,
        signer: this.signer,
        slippage,
        adapter: code,
        rawQuote,
        tokens: [],
        extra: {
          ...extra,
          inAmountNum: this.inAmount,
          outAmountNum: this.outAmount,
        }
      });
      console.log("hash", hash);
      await submitTransactions({
        hash,
        chainCode: this.chainCode,
        inToken: {
          address: this.from.address,
          symbol: this.from.symbol,
          decimals: this.from.decimals,
          amount: amount2Decimals(this.inAmount, this.from.decimals),
          amount_value: this.inAmount,
        },
        outToken: {
          address: this.to.address,
          symbol: this.to.symbol,
          decimals: this.to.decimals,
          amount: amount2Decimals(this.outAmount, this.to.decimals),
          amount_value: this.outAmount,
        },
        usd: 1,
      });
    },
  },
};
</script>

<style lang="scss" scoped>
.swap {
  display: flex;
  flex-flow: row wrap;
}

.form {
  display: flex;
  flex-direction: column;
  margin: 0 10px;
  &.ext {
    padding-top: 16px;
    margin-top: 20px;
  }

  .head {
    display: flex;
    align-items: center;
    justify-content: space-between;

    .title {
      display: flex;
      flex-direction: column;
      h2 {
        font-size: 18px;
        line-height: 1;
        font-family: "Roboto Bold";
        color: var(--color);
        margin-bottom: 15px;
      }

      span {
        font-size: 15px;
        line-height: 1;
        font-family: "Roboto Regular";
        color: var(--color-content);
      }
    }

    .setting {
      display: flex;
      align-items: center;

      div {
        width: 36px;
        height: 36px;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      i {
        font-size: 30px;
        color: var(--color-content);
      }

      .arrow-loading {
        width: 18px;
      }
    }
  }

  .switch {
    height: 42px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 10px auto -15px;

    div {
      cursor: pointer;
      width: 42px;
      height: 42px;

      display: flex;
      align-items: center;
      justify-content: center;
    }

    i {
      font-size: 32px;
      color: var(--color);
    }
  }

  .route {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    margin-right: -10px;

    img {
      width: 21px;
      border-radius: 12px;
      margin-right: 6px;
    }

    span {
      font-size: 14px;
      line-height: 1;
      font-family: "Roboto Medium";
      margin-right: 15px;
    }

    i {
      font-size: 18px;
      transform: rotate(-90deg);
      margin-right: 10px;
    }
  }
}

.aggregator {
  overflow: hidden;
  flex: 1;
  display: flex;
  flex-direction: column;
  padding-top: 20px;
  max-height: 420px;
  overflow: auto;
  padding-right: 30px;
  margin-right: -30px;

  &__list {
    display: flex;
    align-items: flex-start;
    flex-direction: column;
  }

  &__item {
    width: 100%;
    margin-bottom: 16px;
    animation: swing-in-left-fwd 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275)
      both;

    & > div {
      height: 68px;
      background: var(--border-color);
      border: 1px solid var(--border-color);
      border-radius: 10px;
      padding: 8px 16px;
      box-sizing: border-box;

      transition: all 0.3s ease;
      display: flex;
      flex-direction: column;

      &:hover {
        opacity: 0.6;
      }

      &.on {
        border-color: #000;
        background-color: #000;
      }
    }
    .amount {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 8px;

      span {
        color: var(--color);

        &:first-child {
          font-size: 16px;
          font-family: "Roboto Medium";
          line-height: 24px;
        }

        &:last-child {
          font-size: 14px;
          line-height: 24px;
          color: #f84960;
        }

        &.best {
          color: #00c076;
        }
      }
    }

    .fee {
      display: flex;
      align-items: center;

      span {
        font-size: 14px;
        color: var(--color-content);

        &:first-child {
          flex: 1;
        }

        &:last-child {
          margin-left: 6px;
        }
      }
    }
  }
}

.arrow-container {
  position: relative;
  width: 20px;
  height: 20px;
  margin: 0 auto;
}

.arrow {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-top: 10px solid var(--border-color);
  animation: slideDown 1.5s ease infinite;
  z-index: 2;
}

.arrow::after {
  content: "";
  position: absolute;
  top: -13px;
  left: -10px;
  width: 0;
  height: 0;
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-top: 10px solid var(--card);
  z-index: 2;
}

.arrow-bottom {
  margin-top: 8px;
  z-index: 1;
}

@keyframes slideDown {
  0% {
    top: 0;
  }
  50% {
    top: 10px;
  }
  100% {
    top: 0;
  }
}

.waiting {
  flex: 1;
  display: flex;
  flex-direction: column;
  &__head {
    position: relative;

    .logo {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      z-index: 2;

      display: flex;
      align-items: center;
      justify-content: center;
      background: linear-gradient(
        to right,
        #ec5f88 0%,
        #d386ff 30.3762%,
        #6dc2ff 77.7728%,
        #43e2d4 100%
      );
      background-size: 200% 200%;
      animation: gradient-animation 3s linear infinite;

      width: 72px;
      height: 72px;
      border-radius: 36px;

      svg {
        width: 72px;
      }
    }

    .logos {
      width: 100%;
      overflow: hidden;
      position: relative;
      z-index: 1;

      .scroll {
        height: 160px;
        display: flex;
        align-items: center;
        animation: 5s rowleft linear infinite normal;

        & > div {
          margin-right: 20px;
        }
      }
    }
  }

  &__title {
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 20px;

    span {
      font-size: 20px;
      font-family: "Roboto Bold";
    }
  }

  &__feature {
    display: flex;
    align-items: center;
    justify-content: space-between;

    & > div {
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;

      i {
        font-size: 20px;
        line-height: 1.2;
        color: var(--color);
        margin-bottom: 10px;
        transform: scale(1.5);
      }

      span {
        font-size: 14px;
        color: #6dc2ff;
      }
    }
  }

  &__content {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;

    span {
      font-size: 14px;
      line-height: 1.5;
      padding: 20px 0;
      text-align: center;
      color: var(--color-content);
    }
  }
}

@keyframes gradient-animation {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}

@keyframes rowleft {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%);
  }
}

@keyframes swing-in-left-fwd {
  0% {
    transform: rotateX(100deg);
    transform-origin: left;
    opacity: 0;
  }
  100% {
    transform: rotateX(0);
    transform-origin: left;
    opacity: 1;
  }
}
</style>
